package com.edfu.core.testmgt.service.impl;

import static com.edfu.core.commons.exceptions.CommonExceptions.CATEGORY_NAME_NOT_PRESENT;
import static com.edfu.core.commons.exceptions.CommonExceptions.CATEGORY_NOT_FOUND;
import static com.edfu.core.commons.exceptions.CommonExceptions.NULL_ARGUMENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.FORMAT_TYPE_EXISTS;
import static com.edfu.core.testmgt.exceptions.TestExceptions.FORMAT_TYPE_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.FORMAT_TYPE_NAME_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.FORMAT_TYPE_NOT_FOUND;
import static com.edfu.core.testmgt.exceptions.TestExceptions.FORMAT_TYPE_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_CODE_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_EXISTS;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_NOT_FOUND;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_TYPE_EXISTS;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_TYPE_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_TYPE_NAME_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_TYPE_NOT_FOUND;
import static com.edfu.core.testmgt.exceptions.TestExceptions.QUESTION_TYPE_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_BUNDLE_CODE_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_BUNDLE_EXISTS;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_BUNDLE_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_BUNDLE_NAME_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_BUNDLE_NOT_FOUND;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_CODE_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_EXISTS;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_NAME_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_NOT_FOUND;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_TYPE_EXISTS;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_TYPE_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_TYPE_NAME_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.TEST_TYPE_NOT_FOUND;
import static com.edfu.core.testmgt.exceptions.TestExceptions.USER_ID_NOT_PRESENT;
import static com.edfu.core.testmgt.exceptions.TestExceptions.USER_NOT_FOUND;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.edfu.core.commons.beans.Category;
import com.edfu.core.commons.beans.EduObjectType;
import com.edfu.core.commons.exceptions.CategoryException;
import com.edfu.core.commons.exceptions.InvalidArgumentException;
import com.edfu.core.commons.persistence.api.ICategoryMgtPersistence;
import com.edfu.core.commons.persistence.entity.CategoryEntity;
import com.edfu.core.commons.service.api.ICategoryMgtService;
import com.edfu.core.commons.service.api.INotificationService;
import com.edfu.core.coursemgt.persistence.api.ICourseMgtPersistence;
import com.edfu.core.identitymgt.persistence.api.IIdentityMgtPersistence;
import com.edfu.core.identitymgt.persistence.entity.UserEntity;
import com.edfu.core.testmgt.exceptions.TestException;
import com.edfu.core.testmgt.persistence.api.ITestMgtPersistence;
import com.edfu.core.testmgt.persistence.entity.AnswersEntity;
import com.edfu.core.testmgt.persistence.entity.FormatTypeEntity;
import com.edfu.core.testmgt.persistence.entity.ImageEntity;
import com.edfu.core.testmgt.persistence.entity.OptionEntity;
import com.edfu.core.testmgt.persistence.entity.QuestionEntity;
import com.edfu.core.testmgt.persistence.entity.QuestionTypeEntity;
import com.edfu.core.testmgt.persistence.entity.TestBundleEntity;
import com.edfu.core.testmgt.persistence.entity.TestEntity;
import com.edfu.core.testmgt.persistence.entity.TestTypeEntity;
import com.edfu.core.testmgt.persistence.entity.UserTestAnswersEntity;
import com.edfu.core.testmgt.persistence.entity.UserTestSubscriptionEntity;
import com.edfu.core.testmgt.service.api.ITestMgtService;
import com.edfu.core.testmgt.stubs.OptionSet;
import com.edfu.core.testmgt.stubs.TestBundles;
import com.edfu.core.testmgt.util.TestMgtBeanTransformer;
import com.edfu.core.testmgt.util.TestMgtConstants;
import com.edfu.core.testmgt.vo.FormatType;
import com.edfu.core.testmgt.vo.Option;
import com.edfu.core.testmgt.vo.Question;
import com.edfu.core.testmgt.vo.QuestionType;
import com.edfu.core.testmgt.vo.Test;
import com.edfu.core.testmgt.vo.TestBundle;
import com.edfu.core.testmgt.vo.TestType;
import com.edfu.core.testmgt.vo.UserTest;

public class TestMgtServiceImpl implements ITestMgtService {
	
	private static Logger logger = LoggerFactory.getLogger(TestMgtServiceImpl.class);
	private static final String DOWNLOAD_SERVLET_PATH = "downloadServlet/";
	private static final String IMAGE_PATH = "images";
	
	@Autowired
	private ICategoryMgtService categoryMgtService;
	
	@Autowired
	private ICategoryMgtPersistence categoryMgtPersistence;
	
	@Autowired
	private ITestMgtPersistence testMgtPersistence;	
	
	@Autowired
	private ICourseMgtPersistence courseMgtPersistence;
	
	@Autowired
	private IIdentityMgtPersistence identityMgtPersistence;
	
	@Autowired
	private INotificationService notificationService;

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public Test updateTest(Test test) {
		logger.debug("Entered [updateTest] of [TestMgtServiceImpl]");
		checkNull(test);
		// throw exception if Id is not present
		validateTestId(test.getId());
		TestEntity te = getTestEntityAndValidate(test.getId());
		
		if (test.getCode() != null && !test.getCode().equalsIgnoreCase(te.getCode()) && !testMgtPersistence.isTestUnique(test.getCode())) {
			// Test code is changed and changed test code already present in the DB in this case
			logger.warn("Test Code " + te.getCode() + " already present in the DB");
			throw new TestException(TEST_EXISTS);
		}
		
		te = TestMgtBeanTransformer.transformTest(te, test);
		te = testMgtPersistence.updateTest(te);
		
		if (!test.getLogo().equalsIgnoreCase(te.getLogo())) {
			saveTestLogo(test, te);
		}
		logger.debug("Exiting [updateTest] of [TestMgtServiceImpl]");
		
		return TestMgtBeanTransformer.transformTest(te);
	}
	
	private void checkNull(Object obj) {
		if (obj == null) {
			throw new InvalidArgumentException(NULL_ARGUMENT);
		}
	}

	private void validateTestId(long testId) {
		if (testId == 0) {
			logger.warn("Test ID is not present");
			throw new InvalidArgumentException(TEST_ID_NOT_PRESENT);
		}
	}
	
	private void validateTestBundleId(long testBundleId) {
		if (testBundleId == 0) {
			logger.warn("Test Bundle ID is not present");
			throw new InvalidArgumentException(TEST_BUNDLE_ID_NOT_PRESENT);
		}
	}
	
	private void validateTestCode(String testCode) {
		if (StringUtils.isBlank(testCode) || StringUtils.isEmpty(testCode)) {
			logger.warn("Test Code is not present");
			throw new InvalidArgumentException(TEST_CODE_NOT_PRESENT);
		}
	}
	
	private void validateTestName(String testName) {
		if (StringUtils.isBlank(testName) || StringUtils.isEmpty(testName)) {
			logger.warn("Test Name is not present");
			throw new InvalidArgumentException(TEST_NAME_NOT_PRESENT);
		}
	}
	
	private void validateTestBundleCode(String testBundleCode) {
		if (StringUtils.isBlank(testBundleCode) || StringUtils.isEmpty(testBundleCode)) {
			logger.warn("Test Bundle Code is not present");
			throw new InvalidArgumentException(TEST_BUNDLE_CODE_NOT_PRESENT);
		}
	}
	
	private void validateTestBundleName(String testBundleName) {
		if (StringUtils.isBlank(testBundleName) || StringUtils.isEmpty(testBundleName)) {
			logger.warn("Test Bundle Name is not present");
			throw new InvalidArgumentException(TEST_BUNDLE_NAME_NOT_PRESENT);
		}
	}

	private TestEntity getTestEntityAndValidate(long testId) {
		TestEntity te = testMgtPersistence.getTest(testId);
		// check if the test contains any logo
		if (te == null) {
			logger.warn("Test not found");
			throw new TestException(TEST_NOT_FOUND);
		}
		
		alterTestLogo(te);
		return te;
	}

	private void alterTestLogo(TestEntity te) {
		if (StringUtils.isNotBlank(te.getLogo()) && StringUtils.isNotEmpty(te.getLogo())) {
			te.setLogo(DOWNLOAD_SERVLET_PATH + te.getLogo() + "?tid=" + te.getId());
		}
	}
	
	private UserEntity getUserEntityAndValidate(String userId) {
		UserEntity ue = identityMgtPersistence.getUser(userId);
		
		if (ue == null) {
			logger.warn("User not found");
			throw new TestException(USER_NOT_FOUND);
		}
		return ue;
	}
	
	private TestEntity getTestEntityAndValidate(String testCode) {
		TestEntity te = testMgtPersistence.getTest(testCode);
		
		if (te == null) {
			logger.warn("Test not found");
			throw new TestException(TEST_NOT_FOUND);
		}
		alterTestLogo(te);
		return te;
	}
	
	private TestBundleEntity getTestBundleEntityAndValidate(long testBundleId) {
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundleId);
		
		/*if (tbe == null) {
			logger.warn("Test Bundle not found");
			throw new TestException(TEST_BUNDLE_NOT_FOUND);
		}*/
		return tbe;
	}
	
	private TestBundleEntity getTestBundleEntityAndValidate(String testBundleCode) {
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundleCode);
		
		if (tbe == null) {
			logger.warn("Test Bundle not found");
			throw new TestException(TEST_BUNDLE_NOT_FOUND);
		}
		return tbe;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeTest(long testId) {
		logger.debug("Entered [removeTest] of [TestMgtServiceImpl]");
		validateTestId(testId);
		TestEntity te = getTestEntityAndValidate(testId);
		
		testMgtPersistence.removeTest(te);
		notificationService.deleteInterestsNotification(te.getId(), EduObjectType.TEST);
		logger.debug("Exiting [removeTest] of [TestMgtServiceImpl]");
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public Test getTest(long testId) {
		logger.debug("Entered [getTest] of [TestMgtServiceImpl]");
		
		validateTestId(testId);
		TestEntity te = getTestEntityAndValidate(testId);
		
		return (te != null) ? TestMgtBeanTransformer.transformTest(te) : null;
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public Test getTest(String testCode) {
		logger.debug("Entered [getTest] of [TestMgtServiceImpl]");
		
		validateTestCode(testCode);
		TestEntity te = getTestEntityAndValidate(testCode);
		
		return (te != null) ? TestMgtBeanTransformer.transformTest(te) : null;
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public List<Test> getTests(long testBundleId) {
		logger.debug("Entered [getTests] of [TestMgtServiceImpl]");
		
		validateTestBundleId(testBundleId);		
		TestBundleEntity tbe = getTestBundleEntityAndValidate(testBundleId);
		
		List<TestEntity> testEntities = testMgtPersistence.getTests(tbe);
		
		for (TestEntity te : testEntities) {
			alterTestLogo(te);
		}
		return (testEntities != null) ? TestMgtBeanTransformer.getTest(testEntities) : null;
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public List<Test> getTests(String testBundleCode) {
		logger.debug("Entered [getTests] of [TestMgtServiceImpl]");
		
		validateTestBundleCode(testBundleCode);		
		TestBundleEntity tbe = getTestBundleEntityAndValidate(testBundleCode);
		
		List<TestEntity> testEntities = testMgtPersistence.getTests(tbe);
		for (TestEntity te : testEntities) {
			alterTestLogo(te);
		}
		return (testEntities != null) ? TestMgtBeanTransformer.getTest(testEntities) : null;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public TestType createTestType(String testTypeName, String testTypeDesc) {
		logger.debug("Entered [createTestType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(testTypeName) || StringUtils.isEmpty(testTypeName)) {
			logger.warn("Test type name not present");
			throw new InvalidArgumentException(TEST_TYPE_NAME_NOT_PRESENT);
		}
		logger.debug("Exiting [createTestType] of [TestMgtServiceImpl]");
		
		return TestMgtBeanTransformer.transformTestType(testMgtPersistence.createTestType(testTypeName, testTypeDesc));		
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public TestType updateTestType(TestType testType) {
		logger.debug("Entered [updateTestType] of [TestMgtServiceImpl]");
		checkNull(testType);
		validateTestTypeId(testType.getId());
		
		TestTypeEntity tte = testMgtPersistence.getTestType(testType.getId());
		
		if (tte == null) {
			logger.info("Test type is not present");
			throw new TestException(TEST_TYPE_NOT_FOUND);
		}
		if (testType.getName() != null && !testType.getName().equalsIgnoreCase(tte.getName()) && !testMgtPersistence.isTestTypeUnique(testType.getName())) {
			// Test Type Name is changed and changed test type name already present in the DB in this case
			logger.warn("Test type name " + testType.getName() + " already present in the DB");
			throw new TestException(TEST_TYPE_EXISTS);
		}
		
		tte = TestMgtBeanTransformer.transformTestType(tte, testType);
		
		tte = testMgtPersistence.updateTestType(tte);
		logger.debug("Exiting [updateTestType] of [TestMgtServiceImpl]");
		
		return TestMgtBeanTransformer.transformTestType(tte);
	}

	private void validateTestTypeId(long testTypeId) {
		if (testTypeId == 0) {
			logger.warn("Test Type ID is not present");
			throw new InvalidArgumentException(TEST_TYPE_ID_NOT_PRESENT);
		}
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeTestType(long testTypeId) {
		logger.debug("Entered [removeTestType] of [TestMgtServiceImpl]");
		validateTestTypeId(testTypeId);
		
		TestTypeEntity tte = testMgtPersistence.getTestType(testTypeId);
		
		if (tte == null) {
			logger.info("Test type is not present");
			throw new TestException(TEST_TYPE_NOT_FOUND);
		}
		testMgtPersistence.removeTestType(tte);
		logger.debug("Exiting [removeTestType] of [TestMgtServiceImpl]");
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeTestType(String testTypeName) {
		logger.debug("Entered [removeTestType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(testTypeName) || StringUtils.isEmpty(testTypeName)) {
			logger.warn("Test type name not present");
			throw new InvalidArgumentException(TEST_TYPE_NAME_NOT_PRESENT);
		}
		
		TestTypeEntity tte = testMgtPersistence.getTestType(testTypeName);
		
		if (tte == null) {
			logger.info("Test type is not present");
			throw new TestException(TEST_TYPE_NOT_FOUND);
		}
		testMgtPersistence.removeTestType(tte);
		logger.debug("Exiting [removeTestType] of [TestMgtServiceImpl]");
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<TestType> getTestTypes() {
		logger.debug("Entered [getTestTypes] of [TestMgtServiceImpl]");
		
		List<TestTypeEntity> tteList = testMgtPersistence.getTestTypes();
		List<TestType> ttList = null;
		if (tteList != null && tteList.size() > 0) {
			ttList = TestMgtBeanTransformer.transformTestType(tteList);
		}
		
		return ttList;
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public TestType getTestType(long testTypeId) {
		logger.debug("Entered [getTestType] of [TestMgtServiceImpl]");
		
		validateTestTypeId(testTypeId);
		
		TestTypeEntity tte = testMgtPersistence.getTestType(testTypeId);
		
		if (tte == null) {
			logger.info("Test type is not present");
			throw new TestException(TEST_TYPE_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformTestType(tte);
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public TestType getTestType(String testTypeName) {
		logger.debug("Entered [getTestType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(testTypeName) || StringUtils.isEmpty(testTypeName)) {
			logger.warn("Test type name not present");
			throw new InvalidArgumentException(TEST_TYPE_NAME_NOT_PRESENT);
		}
		
		TestTypeEntity tte = testMgtPersistence.getTestType(testTypeName);
		
		if (tte == null) {
			logger.info("Test type is not present");
			throw new TestException(TEST_TYPE_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformTestType(tte);
	}	

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public Question addQuestion(long testId, Question question) {
		logger.debug("Entered [addQuestion] of [TestMgtServiceImpl]");
		
		validateTestId(testId);
		TestEntity te = getTestEntityAndValidate(testId);
		
		return addQuestion(te, question);		
	}
	
	private void validateQuestionCode(String questionCode) {
		if (StringUtils.isBlank(questionCode) || StringUtils.isEmpty(questionCode)) {
			logger.warn("Question Code is not present");
			throw new InvalidArgumentException(QUESTION_CODE_NOT_PRESENT);
		}
	}
	
	private void validateQuestion(String question) {
		if (StringUtils.isBlank(question) || StringUtils.isEmpty(question)) {
			logger.warn("Question is not present");
			throw new InvalidArgumentException(QUESTION_NOT_PRESENT);
		}
	}
	
	private Question addQuestion(TestEntity te, Question question) {
		checkNull(question);
		checkNull(question.getQuestionType());
		checkNull(question.getFormatType());
		validateQuestionCode(question.getCode());
		validateQuestion(question.getQuestion());
		if (question.getQuestionType().getId() == 0 && (StringUtils.isBlank(question.getQuestionType().getName()) || StringUtils.isEmpty(question.getQuestionType().getName()))) {
			logger.warn("Question Type not present");
			throw new InvalidArgumentException(QUESTION_TYPE_NOT_PRESENT);
		}
		
		if (question.getFormatType().getId() == 0 && (StringUtils.isBlank(question.getFormatType().getType()) || StringUtils.isEmpty(question.getFormatType().getType()))) {
			logger.warn("Format Type not present for the question code : " + question.getCode());
			throw new InvalidArgumentException(FORMAT_TYPE_NOT_PRESENT);
		}
		
		if (!testMgtPersistence.isQuestionUnique(question.getCode())) {
			logger.warn("Question Code " + question.getCode() + " already present in the DB");
			throw new TestException(QUESTION_EXISTS);
		}
		
		QuestionTypeEntity qte = null;
		if (question.getQuestionType().getId() != 0) {
			qte = testMgtPersistence.getQuestionType(question.getQuestionType().getId());
		} else {
			qte = testMgtPersistence.getQuestionType(question.getQuestionType().getName());
		}
		
		FormatTypeEntity fte = null;
		if (question.getFormatType().getId() != 0) {
			fte = testMgtPersistence.getFormatType(question.getFormatType().getId());
		} else {
			fte = testMgtPersistence.getFormatType(question.getFormatType().getType());
		}
		
		QuestionEntity qe = TestMgtBeanTransformer.transformQuestion(question);
		qe.setQuestionType(qte);
		qe.setFormatType(fte);
		// if question text contains html, search for images and save, change source and save images
		if (isHTML(question.getQuestion())) {
			qe.setQuestion(question.getQuestion());
			qe = testMgtPersistence.addQuestion(te, qe);
			
			qe.setQuestion(alterImageSource(question.getQuestion(), qe.getId(), 0));
			qe = testMgtPersistence.updateQuestion(qe);
			// saveImages
			saveImages(question.getQuestion(), qe, null);
		} else {
			qe = testMgtPersistence.addQuestion(te, qe);
		}		
		
		// save options
		if (question.getOptions() != null && question.getOptions().size() > 0) {
			OptionEntity optEnt = null;
			FormatTypeEntity ofte = null;
			for (Option option : question.getOptions()) {
				if (option.getFormatType().getId() == 0 && (StringUtils.isBlank(option.getFormatType().getType()) || StringUtils.isEmpty(option.getFormatType().getType()))) {
					logger.warn("Format Type not present for the option : " + option);
					throw new InvalidArgumentException(FORMAT_TYPE_NOT_PRESENT);
				}
				
				if (option.getFormatType().getId() != 0) {
					ofte = testMgtPersistence.getFormatType(option.getFormatType().getId());
				} else {
					ofte = testMgtPersistence.getFormatType(option.getFormatType().getType());
				}
				
				optEnt = TestMgtBeanTransformer.transformOption(option);
				optEnt.setQuestion(qe);
				optEnt.setFormatType(ofte);
				if (isHTML(option.getOpt())) {
					optEnt.setOpt(option.getOpt());
					optEnt = testMgtPersistence.addOption(optEnt);
					
					optEnt.setOpt(alterImageSource(option.getOpt(), qe.getId(),  optEnt.getId()));
					optEnt = testMgtPersistence.updateOption(optEnt);
					// saveImages
					saveImages(option.getOpt(), qe, optEnt);
				} else {
					optEnt = testMgtPersistence.addOption(optEnt);
				}
				AnswersEntity ansEnt = null;
				if (option.isAnswer()) {
					ansEnt = new AnswersEntity();
					ansEnt.setOption(optEnt);
					ansEnt.setQuestion(qe);
					testMgtPersistence.addAnswer(ansEnt);
				}
			}	
			
		}
		
		return TestMgtBeanTransformer.transformQuestion(qe);
	}

	private void saveImages(String text, QuestionEntity qe, OptionEntity optEnt) {
		List<String> imageNames = parseImageNames(text);
		
		for (String imageName : imageNames) {
			try {
				InputStream imageStream = new FileInputStream(new File(IMAGE_PATH + File.separator + imageName));
				ImageEntity ie = new ImageEntity();
				ie.setName(imageName);
				ie.setOption(optEnt);
				ie.setQuestion(qe);
				ie.setContent(IOUtils.toByteArray(imageStream));
				testMgtPersistence.addImage(ie);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				logger.info("Image " + imageName + " doesn't exists");
			} catch (IOException e) {
				e.printStackTrace();
				logger.info("Exception occured while reading stream for the image " + imageName);
			}
		}
	}
	
	private void updateImages(String text, QuestionEntity qe, OptionEntity optEnt) {
		List<String> imageNames = parseImageNames(text);
		
		for (String imageName : imageNames) {
			try {
				InputStream imageStream = new FileInputStream(new File(IMAGE_PATH + File.separator + imageName));
				ImageEntity ie = testMgtPersistence.getImage(qe, optEnt, imageName);
				ie.setName(imageName);
				ie.setOption(optEnt);
				ie.setQuestion(qe);
				ie.setContent(IOUtils.toByteArray(imageStream));
				testMgtPersistence.updateImage(ie);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
				logger.info("Image " + imageName + " doesn't exists");
			} catch (IOException e) {
				e.printStackTrace();
				logger.info("Exception occured while reading stream for the image " + imageName);
			}
		}
	}

	private String alterImageSource(String html, long qid, long oid) {
		if (!StringUtils.containsIgnoreCase(html, "<img")) {
			return html;
		}
//		text = text.replaceAll("src=\"", "src=\"" + DOWNLOAD_SERVLET_PATH);
		
		Document doc = Jsoup.parse(html);
		Iterator<Element> elements = doc.select("img").iterator();
		Element imgElement = null;
		
		while (elements.hasNext()) {
			imgElement = elements.next();
			String imageName = imgElement.attr("src");
			imgElement.attr("src", DOWNLOAD_SERVLET_PATH + imageName + "?qid="+qid+"&oid="+oid);
//			System.out.println(imgElement.attr("src"));
		}
		
		return doc.html();
	}
	
	private boolean isHTML(String text) {
		return !Jsoup.parse(text).text().equals(text);
	}
	
	private List<String> parseImageNames(String html) {
		List<String> imageNames = new ArrayList<String>();
		
		Document doc = Jsoup.parse(html);
		Iterator<Element> elements = doc.select("img").iterator();
		Element imgElement = null;
		while (elements.hasNext()) {
			imgElement = elements.next();
			imageNames.add(imgElement.attr("src"));
		}
		return imageNames;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public Set<Question> addQuestions(long testId, Set<Question> questions) {
		logger.debug("Entered [addQuestions] of [TestMgtServiceImpl]");
		
		validateTestId(testId);
		TestEntity te = getTestEntityAndValidate(testId);
		Set<Question> questionSet = new HashSet<Question>();
		for (Question question : questions) {
			try {
				questionSet.add(addQuestion(te, question));
			} catch (TestException e) {
				e.printStackTrace();
				logger.warn("Error occured while adding question : " + question);
			}			
		}
		
		logger.debug("Exiting [addQuestions] of [TestMgtServiceImpl]");
		return questionSet;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public Question updateQuestion(Question question) {
		logger.debug("Entered [updateQuestion] of [TestMgtServiceImpl]");
		checkNull(question);
		validateQuestionId(question.getId());
		QuestionEntity qe = testMgtPersistence.getQuestion(question.getId());
		if (qe == null) {
			logger.warn("Question not found");
			throw new TestException(QUESTION_NOT_FOUND);
		}
		
		if (question.getCode() != null && !question.getCode().equalsIgnoreCase(qe.getCode()) && !testMgtPersistence.isQuestionUnique(question.getCode())) {
			// Question code is changed and changed question code already present in the DB in this case
			logger.warn("Question Code " + qe.getCode() + " already present in the DB");
			throw new TestException(QUESTION_EXISTS);
		}
		
		qe = TestMgtBeanTransformer.transformQuestion(qe, question);
		if (isHTML(question.getQuestion())) {
			qe.setQuestion(alterImageSource(question.getQuestion(), qe.getId(), 0));
			qe = testMgtPersistence.updateQuestion(qe);
			
			// saveImages
			updateImages(question.getQuestion(), qe, null);
		} else {
			qe = testMgtPersistence.updateQuestion(qe);
		}	
		
		logger.debug("Exiting [updateQuestion] of [TestMgtServiceImpl]");
		
		return TestMgtBeanTransformer.transformQuestion(qe);
	}
	
	private void validateQuestionId(long questionId) {
		if (questionId == 0) {
			logger.warn("Question ID is not present");
			throw new InvalidArgumentException(QUESTION_ID_NOT_PRESENT);
		}
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public QuestionType createQuestionType(String questionTypeName, String questionTypeDesc) {
		logger.debug("Entered [createQuestionType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(questionTypeName) || StringUtils.isEmpty(questionTypeName)) {
			logger.warn("Question type name not present");
			throw new InvalidArgumentException(QUESTION_TYPE_NAME_NOT_PRESENT);
		}
		
		logger.debug("Exiting [createQuestionType] of [TestMgtServiceImpl]");
		return TestMgtBeanTransformer.transformQuestionType(testMgtPersistence.createQuestionType(questionTypeName, questionTypeDesc));		
		
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public QuestionType updateQuestionType(QuestionType questionType) {
		logger.debug("Entered [updateQuestionType] of [TestMgtServiceImpl]");
		checkNull(questionType);
		validateQuestionTypeId(questionType.getId());
		
		QuestionTypeEntity qte = testMgtPersistence.getQuestionType(questionType.getId());
		
		if (qte == null) {
			logger.info("Question type is not present");
			throw new TestException(QUESTION_TYPE_NOT_FOUND);
		}
		if (questionType.getName() != null && !questionType.getName().equalsIgnoreCase(qte.getName()) && !testMgtPersistence.isQuestionTypeUnique(questionType.getName())) {
			// Question Type Name is changed and changed question type name already present in the DB in this case
			logger.warn("Question type name " + questionType.getName() + " already present in the DB");
			throw new TestException(QUESTION_TYPE_EXISTS);
		}
		
		qte = TestMgtBeanTransformer.transformQuestionType(qte, questionType);
		
		qte = testMgtPersistence.updateQuestionType(qte);
		logger.debug("Exiting [updateQuestionType] of [TestMgtServiceImpl]");
		
		return TestMgtBeanTransformer.transformQuestionType(qte);
	}

	private void validateQuestionTypeId(long questionTypeId) {
		if (questionTypeId == 0) {
			logger.warn("Question Type ID is not present");
			throw new InvalidArgumentException(QUESTION_TYPE_ID_NOT_PRESENT);
		}
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeQuestionType(long questionTypeId) {
		logger.debug("Entered [removeQuestionType] of [TestMgtServiceImpl]");
		validateQuestionTypeId(questionTypeId);
		
		QuestionTypeEntity qte = testMgtPersistence.getQuestionType(questionTypeId);
		
		if (qte == null) {
			logger.info("Question type is not present");
			throw new TestException(QUESTION_TYPE_NOT_FOUND);
		}
		testMgtPersistence.removeQuestionType(qte);
		logger.debug("Exiting [removeQuestionType] of [TestMgtServiceImpl]");
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeQuestionType(String questionTypeName) {
		logger.debug("Entered [removeQuestionType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(questionTypeName) || StringUtils.isEmpty(questionTypeName)) {
			logger.warn("Question type name not present");
			throw new InvalidArgumentException(QUESTION_TYPE_NAME_NOT_PRESENT);
		}
		
		QuestionTypeEntity qte = testMgtPersistence.getQuestionType(questionTypeName);
		
		if (qte == null) {
			logger.info("Question type is not present");
			throw new TestException(QUESTION_TYPE_NOT_FOUND);
		}
		testMgtPersistence.removeQuestionType(qte);
		logger.debug("Exiting [removeQuestionType] of [TestMgtServiceImpl]");
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<QuestionType> getQuestionTypes() {
		logger.debug("Entered [getQuestionTypes] of [TestMgtServiceImpl]");
		
		List<QuestionTypeEntity> qteList = testMgtPersistence.getQuestionTypes();
		List<QuestionType> qtList = null;
		if (qteList != null && qteList.size() > 0) {
			qtList = TestMgtBeanTransformer.transformQuestionType(qteList);
		}
		
		return qtList;
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public QuestionType getQuestionType(long questionTypeId) {
		logger.debug("Entered [getQuestionType] of [TestMgtServiceImpl]");
		
		validateQuestionTypeId(questionTypeId);
		
		QuestionTypeEntity qte = testMgtPersistence.getQuestionType(questionTypeId);
		
		if (qte == null) {
			logger.info("Question type is not present");
			throw new TestException(QUESTION_TYPE_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformQuestionType(qte);
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public QuestionType getQuestionType(String questionTypeName) {
		logger.debug("Entered [getQuestionType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(questionTypeName) || StringUtils.isEmpty(questionTypeName)) {
			logger.warn("Question type name not present");
			throw new InvalidArgumentException(QUESTION_TYPE_NAME_NOT_PRESENT);
		}
		
		QuestionTypeEntity qte = testMgtPersistence.getQuestionType(questionTypeName);
		
		if (qte == null) {
			logger.info("Question type is not present");
			throw new TestException(QUESTION_TYPE_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformQuestionType(qte);
	}
	
	private Test createTest(TestBundleEntity tbe, long testBundleId, Test test) {
		logger.debug("Entered [createTest] of [TestMgtServiceImpl]");
		checkNull(test);
		checkNull(test.getTestType());
		checkNull(test.getCategories());
		/*if (tbe == null) {
			validateTestBundleId(testBundleId);
		}*/
		validateTestCode(test.getCode());
		validateTestName(test.getName());
		
		if (!testMgtPersistence.isTestUnique(test.getCode())) {
			logger.warn("Test Code " + test.getCode() + " already present");
			throw new TestException(TEST_EXISTS);
		}
		
		if (test.getTestType().getId() == 0 && (StringUtils.isEmpty(test.getTestType().getName()) || StringUtils.isBlank(test.getTestType().getName()))) {
			logger.warn("Test Type not found");
			throw new TestException(TEST_TYPE_NOT_FOUND);
		}
		
		if(test.getCategories().size() == 0) {
			logger.warn("Test Categories not found");
			throw new TestException(CATEGORY_NOT_FOUND);
		}
		/*if (test.getCategory().getId() == 0 && (StringUtils.isEmpty(test.getCategory().getName()) || StringUtils.isBlank(test.getCategory().getName()))) {
			logger.warn("Test Category not found");
			throw new TestException(CATEGORY_NOT_FOUND);
		}*/
		
		if (tbe == null) {
			tbe = getTestBundleEntityAndValidate(testBundleId);
		}
		TestEntity te = TestMgtBeanTransformer.transformTest(test);
		te.setTestBundle(tbe);
		TestTypeEntity tte = null;
		
		if (test.getTestType().getId() != 0) {
			tte = testMgtPersistence.getTestType(test.getTestType().getId());
		}  else {
			tte = testMgtPersistence.getTestType(test.getTestType().getName());
		}
		te.setTestType(tte);
		
		CategoryEntity ce = null;
		Set<CategoryEntity> categoryEntities = new HashSet<CategoryEntity>();
		
		for (Category category : test.getCategories()) {
			if (category.getId() != 0) {
				ce = categoryMgtPersistence.getCategory(category.getId());
			}  else {
				ce = categoryMgtPersistence.getCategory(category.getName());
			}
			categoryEntities.add(ce);
		}
		
		te.setCategories(categoryEntities);
		te.setCreatedDate(Calendar.getInstance().getTimeInMillis());
		te = testMgtPersistence.createTest(te);
		
		notificationService.addInterestsNotification(te.getId(), EduObjectType.TEST, test.getCategories());
		
		// insert logo
		if (StringUtils.isNotBlank(test.getLogo()) && StringUtils.isNotEmpty(test.getLogo())) {
			saveTestLogo(test, te);
		}
		
		if (test.getQuestions() != null && test.getQuestions().size() > 0) {
			for (Question question : test.getQuestions()) {
				addQuestion(te, question);
			}
		}
		
		logger.debug("Exiting [createTest] of [TestMgtServiceImpl]");
		
		return TestMgtBeanTransformer.transformTest(te);
	}

	private void saveTestLogo(Test test, TestEntity te) {
		try {
			InputStream imageStream = new FileInputStream(new File(IMAGE_PATH + File.separator + test.getLogo()));
			ImageEntity ie = new ImageEntity();
			ie.setName(test.getLogo());
			ie.setTest(te);
			ie.setContent(IOUtils.toByteArray(imageStream));
			testMgtPersistence.addImage(ie);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			logger.info("Image " + test.getLogo() + " doesn't exists");
		} catch (IOException e) {
			e.printStackTrace();
			logger.info("Exception occured while reading stream for the image " + test.getLogo());
		}
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public Test createTest(long testBundleId, Test test) {
		return createTest(null, testBundleId, test);
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
	public TestBundle createTestBundle(TestBundle testBundle) {
		logger.debug("Entered [createTestBundle] of [TestMgtServiceImpl]");
		
		validateTestBundleCode(testBundle.getCode());
		validateTestBundleName(testBundle.getName());
		
		if (!testMgtPersistence.isTestBundleUnique(testBundle.getCode())) {
			logger.warn("TestBundle code " + testBundle.getCode() + " already present in DB");
			throw new TestException(TEST_BUNDLE_EXISTS);
		}
		
		TestBundleEntity tbe = testMgtPersistence.createTestBundle(TestMgtBeanTransformer.transformTestBundle(testBundle));
		
		if (testBundle.getTests() != null && testBundle.getTests().size() > 0) {
			for (Test test : testBundle.getTests()) {
				try {
					createTest(tbe, 0, test);
				} catch (TestException e) {
					e.printStackTrace();
					logger.warn("Exception occured while adding test : " + test);
				}
			}
		}
		logger.debug("Exiting [createTestBundle] of [TestMgtServiceImpl]");	
		
		return TestMgtBeanTransformer.transformTestBundle(tbe);
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public TestBundle updateTestBundle(TestBundle testBundle) {
		logger.debug("Entered [updateTestBundle] of [TestMgtServiceImpl]");
		
		checkNull(testBundle);
		validateTestBundleId(testBundle.getId());
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundle.getId());
		
		if (tbe == null) {
			logger.warn("TestBundle with Id " + testBundle.getId() + " doesn't exists");
			throw new TestException(TEST_BUNDLE_NOT_FOUND);
		}
		
		tbe = TestMgtBeanTransformer.transformTestBundle(tbe, testBundle);
		testMgtPersistence.updateTestBundle(tbe);
		logger.debug("Exiting [updateTestBundle] of [TestMgtServiceImpl]");
		
		return testBundle;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeTestBundle(long testBundleId) {
		logger.debug("Entered [removeTestBundle] of [TestMgtServiceImpl]");
		
		validateTestBundleId(testBundleId);
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundleId);
		
		if (tbe == null) {
			logger.warn("TestBundle with Id " + testBundleId + " doesn't exists");
			throw new TestException(TEST_BUNDLE_NOT_FOUND);
		}		
		
		testMgtPersistence.removeTestBundle(tbe);
		logger.debug("Exiting [removeTestBundle] of [TestMgtServiceImpl]");		
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeTestBundle(String testBundleCode) {
		logger.debug("Entered [removeTestBundle] of [TestMgtServiceImpl]");
		
		validateTestBundleCode(testBundleCode);
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundleCode);
		
		if (tbe == null) {
			logger.warn("TestBundle with code " + testBundleCode + " doesn't exists");
			throw new TestException(TEST_BUNDLE_NOT_FOUND);
		}		
		
		testMgtPersistence.removeTestBundle(tbe);
		logger.debug("Exiting [removeTestBundle] of [TestMgtServiceImpl]");		
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public TestBundle getTestBundle(long testBundleId) {
		logger.debug("Entered [getTestBundle] of [TestMgtServiceImpl]");
		
		validateTestBundleId(testBundleId);
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundleId);
		TestBundle tb = null;
		
		if (tbe != null) {
			tb = TestMgtBeanTransformer.transformTestBundle(tbe);
		}
		
		return tb;
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public TestBundle getTestBundle(String testBundleCode) {
		logger.debug("Entered [getTestBundle] of [TestMgtServiceImpl]");
		
		validateTestBundleCode(testBundleCode);
		TestBundleEntity tbe = testMgtPersistence.getTestBundle(testBundleCode);
		TestBundle tb = null;
		
		if (tbe != null) {
			tb = TestMgtBeanTransformer.transformTestBundle(tbe);
		}
		
		return tb;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeQuestion(long questionId) {
		logger.debug("Entered [removeQuestion] of [TestMgtServiceImpl]");
		
		validateQuestionId(questionId);
		
		QuestionEntity qe = testMgtPersistence.getQuestion(questionId);
		
		if (qe == null) {
			logger.warn("Question not found");
			throw new TestException(QUESTION_NOT_FOUND);
		}
		testMgtPersistence.removeQuestion(qe);
		logger.debug("Exiting [removeQuestion] of [TestMgtServiceImpl]");		
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public Set<Question> getQuestions(long testId) {
		logger.debug("Entered [getQuestions] of [TestMgtServiceImpl]");
		
		validateTestId(testId);
		TestEntity te = getTestEntityAndValidate(testId);
		
		return TestMgtBeanTransformer.transformQuestion(te.getQuestions());
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public Set<Question> getQuestionsWithAnswers(long testId) {
		logger.debug("Entered [getQuestions] of [TestMgtServiceImpl]");
		
		validateTestId(testId);
		TestEntity te = getTestEntityAndValidate(testId);
		
		return TestMgtBeanTransformer.transformQuestionWithAnswers(te.getQuestions());
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public Set<Option> getAnswers(long questionId) {
		logger.debug("Entered [getAnswers] of [TestMgtServiceImpl]");		
		
		validateQuestionId(questionId);
		
		QuestionEntity qe = testMgtPersistence.getQuestion(questionId);
		if (qe == null) {
			logger.warn("Question not found");
			throw new TestException(QUESTION_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformOption(testMgtPersistence.getAnswers(qe));
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED )
	public void uploadTestBundle() {
		try {
			JAXBContext jaxbContext = JAXBContext.newInstance(TestBundles.class);
			TestBundles testBundles = (TestBundles) jaxbContext.createUnmarshaller().unmarshal(new File("TestBundle.xml"));
			
			if (testBundles != null && testBundles.getTestBundle() != null && testBundles.getTestBundle().size() > 0) {
				for (com.edfu.core.testmgt.stubs.TestBundle testBundleStub : testBundles.getTestBundle()) {
					try {
						createTestBundle(transformTestBundle(testBundleStub));
					} catch (TestException e) {
						e.printStackTrace();
					} 
				}
			}
			System.out.println(testBundles.getTestBundle().size());
		} catch (JAXBException e) {
			e.printStackTrace();
		} 
		
	}

	private TestBundle transformTestBundle(com.edfu.core.testmgt.stubs.TestBundle testBundleStub) {
		TestBundle testBundle = new TestBundle();
		
		testBundle.setActive(testBundleStub.getActive().intValue() == 1 ? true : false);
		
		if (testBundleStub.getCode() == null) {
			testBundle.setCode(RandomStringUtils.randomAlphanumeric(3));
		} else {
			testBundle.setCode(testBundleStub.getCode());
		}
		testBundle.setDesc(testBundleStub.getDescription());
		testBundle.setName(testBundleStub.getName());
		testBundle.setPublisher(testBundleStub.getPublisher());
		
		Set<Test> testSet = new HashSet<Test>();
		Test test = null;
		for (com.edfu.core.testmgt.stubs.Test testStub : testBundleStub.getTest()) {
			test = new Test();
			test.setActive(testStub.getActive().intValue() == 1 ? true : false);
			if (testStub.getCode() == null) {
				test.setCode(RandomStringUtils.randomAlphanumeric(3));
			} else {
				test.setCode(testStub.getCode());
			}
			test.setAllowChangeAnswers(testStub.isAllowChangeAnswers());
			test.setAllowReattempt(testStub.isAllowReattempt());
			test.setAllowReview(testStub.isAllowReview());
			test.setCreatedBy(testStub.getCreatedBy());
			test.setCreatedDate(Calendar.getInstance().getTimeInMillis());
			test.setDesc(testStub.getDescription());
			test.setDuration(testStub.getDuration().longValue());
			test.setInstructions(testStub.getInstructions());
			test.setName(testStub.getName());
			test.setLogo(testStub.getLogo());
			test.setNegativeMarks(testStub.getNegativeMarks().doubleValue());
			test.setPassMarks(testStub.getPassMarks().doubleValue());
			test.setPositiveMarks(testStub.getPositiveMarks().doubleValue());
			test.setDuration(testStub.getDuration().longValue());
			
			Set<Question> questionSet = new HashSet<Question>();
			Question question = null;
			for (com.edfu.core.testmgt.stubs.Question questionStub : testStub.getQuestion()) {
				question = new Question();
				
				question.setActive(questionStub.getActive().intValue() == 1 ? true : false);
				if (questionStub.getCode() == null) {
					question.setCode(RandomStringUtils.randomAlphanumeric(4));
				} else {
					question.setCode(questionStub.getCode());
				}
				question.setDescription(questionStub.getDescription());
				question.setDuration(questionStub.getDuration().longValue());
				FormatType formatType = new FormatType();
				formatType.setType(questionStub.getFormatType());
				question.setFormatType(formatType);
				question.setNegativeMarks(questionStub.getNegativeMarks().doubleValue());
				
				if (questionStub.getDuration() != null) {
					question.setDuration(questionStub.getDuration().longValue());
				}
				
				Set<Option> optionSet = null;
//				Set<Option> answerSet = null;
				if (questionStub.getOptionSet() != null && questionStub.getOptionSet().size() > 0) {
					for (OptionSet os : questionStub.getOptionSet()) {
						if (os != null && os.getOption() != null && os.getOption().size() > 0) {
							optionSet = new HashSet<Option>();
//							answerSet = new HashSet<Option>();
							Option option = null;
							FormatType ft = null;
							for (com.edfu.core.testmgt.stubs.Option optionStub : os.getOption()) {
								option = new Option();
								option.setOpt(optionStub.getValue());
								ft = new FormatType();
								ft.setType(optionStub.getFormatType());
								option.setFormatType(ft);
								option.setAnswer("Y".equalsIgnoreCase(optionStub.getAns()) ? true : false);
								optionSet.add(option);
								
								/*if ("Y".equalsIgnoreCase(optionStub.getAns())) {
									answerSet.add(option);
								}*/
							}
						}
					}
				}
				
				
				question.setOptions(optionSet);
//				question.setAnswers(answerSet);
				question.setPostivieMarks(questionStub.getPositiveMarks().doubleValue());
				
				question.setQuestion(questionStub.getText());
				QuestionType questionType = new QuestionType();
				questionType.setName(questionStub.getQuestionType());
				question.setQuestionType(questionType);
				question.setSolution(questionStub.getSolution());
				
				questionSet.add(question);
			}
			test.setQuestions(questionSet);
			Set<Category> categorySet = new HashSet<Category>();
			Category category = new Category();
			category.setName(testStub.getTestCategory());
			categorySet.add(category); //TODO: Supporting only one category for XML upload now.. need to change the stubs
			TestType testType = new TestType();
			testType.setName(testStub.getTestType());
			test.setTestType(testType);
			test.setCategories(categorySet);
			test.setTimeBased(testStub.isTimeBased());
			test.setTotalMarks(testStub.getTotalMarks().doubleValue());
			
			testSet.add(test);
		}
		testBundle.setTests(testSet);
		
		return testBundle;
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void subscribeTest(UserTest userTest) {
		logger.debug("Entered [subscribeTest] of [TestMgtServiceImpl]");
		checkNull(userTest);
		checkNull(userTest.getTest());
		checkNull(userTest.getUser());
		checkNull(userTest.getTestDate());
		validateTestId(userTest.getTest().getId());
		valiateUserId(userTest.getUser().getUserId());
		
		TestEntity te = getTestEntityAndValidate(userTest.getTest().getId());
		UserEntity ue = getUserEntityAndValidate(userTest.getUser().getUserId());
		
		UserTestSubscriptionEntity utse = new UserTestSubscriptionEntity();
		utse.setTest(te);
		utse.setUser(ue);
		utse.setSubscribedDate(Calendar.getInstance().getTimeInMillis());
		utse.setTestDate(userTest.getTestDate().getTime());
		testMgtPersistence.subscribeTest(utse);
		logger.debug("Exiting [subscribeTest] of [TestMgtServiceImpl]");
	}

	private void valiateUserId(String userId) {
		if (StringUtils.isBlank(userId) || StringUtils.isEmpty(userId)) {
			logger.warn("UserId is not present");
			throw new InvalidArgumentException(USER_ID_NOT_PRESENT);
		}
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void unsubscribeTest(UserTest userTest) {
		logger.debug("Entered [unsubscribeTest] of [TestMgtServiceImpl]");
		
		testMgtPersistence.unsubscribeTest(getUserTestSubscriptionAndValidate(userTest));
		logger.debug("Exiting [unsubscribeTest] of [TestMgtServiceImpl]");
	}
	
	private UserTestSubscriptionEntity getUserTestSubscriptionAndValidate(UserTest userTest) {
		checkNull(userTest);
		checkNull(userTest.getTest());
		checkNull(userTest.getUser());
		checkNull(userTest.getTestDate());
		validateTestId(userTest.getTest().getId());
		valiateUserId(userTest.getUser().getUserId());
		
		TestEntity te = getTestEntityAndValidate(userTest.getTest().getId());
		UserEntity ue = getUserEntityAndValidate(userTest.getUser().getUserId());
		
		return testMgtPersistence.getUserTestSubscriptionEntity(ue, te, userTest.getTestDate().getTime());
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void addOrUpdateUserTestDetails(UserTest userTest) {
		logger.debug("Entered [addUserTestDetails] of [TestMgtServiceImpl]");
		UserTestSubscriptionEntity utse = getUserTestSubscriptionAndValidate(userTest);
		utse.setGrade(userTest.getGrade());
		utse.setPass(userTest.isPass());
		utse.setPercentage(userTest.getPercentage());
		utse.setPresent(userTest.isPresent());
		
		testMgtPersistence.addOrUpdateUserTestDetails(utse);
		logger.debug("Exiting [addUserTestDetails] of [TestMgtServiceImpl]");
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public UserTest getUserTestDetails(UserTest userTest) {
		logger.debug("Entered [getUserTestDetails] of [TestMgtServiceImpl]");
		
		UserTestSubscriptionEntity utse = getUserTestSubscriptionAndValidate(userTest);
		
		return TestMgtBeanTransformer.transformUserTestDetails(utse);
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, rollbackFor = { TestException.class })
	public UserTest getUserTestDetailsWithAnswers(UserTest userTest) {
		logger.debug("Entered [getUserTestDetailsWithAnswers] of [TestMgtServiceImpl]");		
		
		UserTestSubscriptionEntity utse = getUserTestSubscriptionAndValidate(userTest);
		
		return TestMgtBeanTransformer.transformUserTestDetails(utse);
	}
	
	@Override
	public UserTest processUserTestResults(UserTest userTest) {
		UserTestSubscriptionEntity utse = getUserTestSubscriptionAndValidate(userTest);
		
		Set<Question> baseQuestions = TestMgtBeanTransformer.transformQuestionWithAnswers(utse.getTest().getQuestions());
		List<UserTestAnswersEntity> userTestAnswers = testMgtPersistence.getUserTestAnswers(utse.getTest(), utse.getUser(), utse.getTestDate());
		Map<Question, Set<Option>> userAnswersMap = new HashMap<Question, Set<Option>>();
		Set<Option> options = null;
		double totalMarks = 0.0;
		for (UserTestAnswersEntity utae : userTestAnswers) {
			Question question = TestMgtBeanTransformer.transformQuestion(utae.getQuestion());
			
			if (userAnswersMap.containsKey(question)) {
				options = userAnswersMap.get(question);				
			} else {
				options = new HashSet<Option>();				
			}
			options.add(TestMgtBeanTransformer.transformOption(utae.getOption()));
			userAnswersMap.put(question, options);
		}
		
		for (Question baseQuestion : baseQuestions) {
			Set<Option> userAnswers = userAnswersMap.get(baseQuestion);
			if (userAnswers == null || userAnswers.size() == 0) {
				// User didn't answered this question
				continue;
			} else if (baseQuestion.getQuestionType().getName().equalsIgnoreCase(TestMgtConstants.QUESTION_TYPE_SINGLE_OPTION)) {
				Option baseAns = (Option) baseQuestion.getAnswers().toArray()[0];
				Option userAns = (Option) userAnswers.toArray()[0];
				if (baseAns.getOpt().equals(userAns.getOpt())) {
					// user answer is correct
					if (baseQuestion.getPostivieMarks() == 0.0) {
						// Question specific marks not assigned
						totalMarks += utse.getTest().getPositiveMarks();
					} else {
						totalMarks += baseQuestion.getPostivieMarks();
					}					
				} else {
					totalMarks = processNegativeMarks(utse, totalMarks,
							baseQuestion);
				}
			} else if (baseQuestion.getQuestionType().getName().equalsIgnoreCase(TestMgtConstants.QUESTION_TYPE_MULTIPLE_OPTIONS)) {
				if (baseQuestion.getAnswers().size() == userAnswers.size()) {
					boolean isWrong = false;
					for (Option baseAns : baseQuestion.getAnswers()) {
						if (!hasOption(baseAns, userAnswers)) {
							isWrong = true;
							break;
						}
					}
					
					if (isWrong) {
						totalMarks = processNegativeMarks(utse, totalMarks, baseQuestion);
					} else {
						totalMarks += baseQuestion.getPostivieMarks();
					}
				} else {
					totalMarks = processNegativeMarks(utse, totalMarks, baseQuestion);
				}
			}			
		}
		
		utse.setMarks(totalMarks);
		if (totalMarks >= utse.getTest().getPassMarks()) {
			// user has passed the test
			utse.setPass(true);
		} else {
			// user has failed the test
			utse.setPass(false);
		}
		
		utse = testMgtPersistence.updateUserTestSubscription(utse);
		
		return TestMgtBeanTransformer.transformUserTestDetails(utse);
	}

	private boolean hasOption(Option baseAns, Set<Option> userAnswers) {
		for (Option userAnswer : userAnswers) {
			if (baseAns.getOpt().equals(userAnswer.getOpt())) {
				return true;
			}
		}
		return false;
	}

	private double processNegativeMarks(UserTestSubscriptionEntity utse, double totalMarks, Question baseQuestion) {
		if (baseQuestion.getNegativeMarks() != 0.0) {
			totalMarks = totalMarks - baseQuestion.getNegativeMarks();
		} else if (utse.getTest().getNegativeMarks() != 0.0) {
			totalMarks = totalMarks - utse.getTest().getNegativeMarks();
		}
		return totalMarks;
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void submitUserTestAnswers(UserTest userTest) {
		logger.debug("Entered [submitUserTestAnswers] of [TestMgtServiceImpl]");
		
		checkNull(userTest);
		checkNull(userTest.getTest());
		checkNull(userTest.getUser());
		checkNull(userTest.getTestDate());
		checkNull(userTest.getUserAnswers());
		validateTestId(userTest.getTest().getId());
		valiateUserId(userTest.getUser().getUserId());
		
		TestEntity te = getTestEntityAndValidate(userTest.getTest().getId());
		UserEntity ue = getUserEntityAndValidate(userTest.getUser().getUserId());
		
		UserTestAnswersEntity utae = null;
		QuestionEntity qe = null;
		OptionEntity oe = null;
		for (Long questionId : userTest.getUserAnswers().keySet()) {
			qe = testMgtPersistence.getQuestion(questionId);
			
			if (qe == null) {
				logger.info("There is no question with Id : " + questionId);
				continue;
			}
			
			for (Long optionId : userTest.getUserAnswers().get(questionId)) {
				oe = testMgtPersistence.getOption(optionId);
				if (oe == null) {
					logger.info("There is no option with Id : " + optionId);
					continue;
				}
				
				utae = new UserTestAnswersEntity();
				utae.setOption(oe);
				utae.setQuestion(qe);
				utae.setTest(te);
				utae.setTestDate(userTest.getTestDate().getTime());
				utae.setUser(ue);
				
				testMgtPersistence.submitUserTestAnswers(utae);
			}			
		}
		
		logger.debug("Exiting [submitUserTestAnswers] of [TestMgtServiceImpl]");
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public FormatType createFormatType(String formatTypeName) {
		logger.debug("Entered [createFormatType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(formatTypeName) || StringUtils.isEmpty(formatTypeName)) {
			logger.warn("Format type name not present");
			throw new InvalidArgumentException(FORMAT_TYPE_NAME_NOT_PRESENT);
		}
		
		logger.debug("Exiting [createFormatType] of [TestMgtServiceImpl]");
		return TestMgtBeanTransformer.transformFormatType(testMgtPersistence.createFormatType(formatTypeName));		
		
	}

	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public FormatType updateFormatType(FormatType formatType) {
		logger.debug("Entered [updateFormatType] of [TestMgtServiceImpl]");
		checkNull(formatType);
		validateFormatTypeId(formatType.getId());
		
		FormatTypeEntity fte = testMgtPersistence.getFormatType(formatType.getId());
		
		if (fte == null) {
			logger.info("Format type is not present");
			throw new TestException(FORMAT_TYPE_NOT_FOUND);
		}
		if (formatType.getType() != null && !formatType.getType().equalsIgnoreCase(fte.getType()) && !testMgtPersistence.isFormatTypeUnique(formatType.getType())) {
			// Format Type Name is changed and changed format type name already present in the DB in this case
			logger.warn("Format type name " + formatType.getType() + " already present in the DB");
			throw new TestException(FORMAT_TYPE_EXISTS);
		}
		
		fte = TestMgtBeanTransformer.transformFormatType(fte, formatType);
		
		testMgtPersistence.updateFormatType(fte);
		logger.debug("Exiting [updateFormatType] of [TestMgtServiceImpl]");
		
		return formatType;
	}

	private void validateFormatTypeId(long formatTypeId) {
		if (formatTypeId == 0) {
			logger.warn("Format Type ID is not present");
			throw new InvalidArgumentException(FORMAT_TYPE_ID_NOT_PRESENT);
		}
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeFormatType(long formatTypeId) {
		logger.debug("Entered [removeFormatType] of [TestMgtServiceImpl]");
		validateFormatTypeId(formatTypeId);
		
		FormatTypeEntity fte = testMgtPersistence.getFormatType(formatTypeId);
		
		if (fte == null) {
			logger.info("Format type is not present");
			throw new TestException(FORMAT_TYPE_NOT_FOUND);
		}
		testMgtPersistence.removeFormatType(fte);
		logger.debug("Exiting [removeFormatType] of [TestMgtServiceImpl]");
	}
	
	@Override
	@Transactional(readOnly = false, propagation = Propagation.REQUIRED, rollbackFor = { TestException.class })
	public void removeFormatType(String formatTypeName) {
		logger.debug("Entered [removeFormatType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(formatTypeName) || StringUtils.isEmpty(formatTypeName)) {
			logger.warn("Format type name not present");
			throw new InvalidArgumentException(FORMAT_TYPE_NAME_NOT_PRESENT);
		}
		
		FormatTypeEntity fte = testMgtPersistence.getFormatType(formatTypeName);
		
		if (fte == null) {
			logger.info("Format type is not present");
			throw new TestException(FORMAT_TYPE_NOT_FOUND);
		}
		testMgtPersistence.removeFormatType(fte);
		logger.debug("Exiting [removeFormatType] of [TestMgtServiceImpl]");
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public List<FormatType> getFormatTypes() {
		logger.debug("Entered [getFormatTypes] of [TestMgtServiceImpl]");
		
		List<FormatTypeEntity> fteList = testMgtPersistence.getFormatTypes();
		List<FormatType> ftList = null;
		if (fteList != null && fteList.size() > 0) {
			ftList = TestMgtBeanTransformer.transformFormatType(fteList);
		}
		
		return ftList;
	}

	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public FormatType getFormatType(long formatTypeId) {
		logger.debug("Entered [getFormatType] of [TestMgtServiceImpl]");
		
		validateFormatTypeId(formatTypeId);
		
		FormatTypeEntity fte = testMgtPersistence.getFormatType(formatTypeId);
		
		if (fte == null) {
			logger.info("Format type is not present");
			throw new TestException(FORMAT_TYPE_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformFormatType(fte);
	}
	
	@Override
	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
	public FormatType getFormatType(String formatTypeName) {
		logger.debug("Entered [getFormatType] of [TestMgtServiceImpl]");
		
		if (StringUtils.isBlank(formatTypeName) || StringUtils.isEmpty(formatTypeName)) {
			logger.warn("Format type name not present");
			throw new InvalidArgumentException(FORMAT_TYPE_NAME_NOT_PRESENT);
		}
		
		FormatTypeEntity fte = testMgtPersistence.getFormatType(formatTypeName);
		
		if (fte == null) {
			logger.info("Format type is not present");
			throw new TestException(FORMAT_TYPE_NOT_FOUND);
		}
		
		return TestMgtBeanTransformer.transformFormatType(fte);
	}

	@Override
	public List<Test> getTestDetails() {
		return TestMgtBeanTransformer.transformBasicTestDetails(testMgtPersistence.getTestDetails());
	}

	@Override
	public List<Test> getSubscribedTests(String userId) {
		valiateUserId(userId);
		
		UserEntity ue = getUserEntityAndValidate(userId);
		
		return TestMgtBeanTransformer.transformBasicTestDetails(testMgtPersistence.getSubscribedTests(ue));
	}

    @Override
	public void associateTestToCategory(String categoryCode, String testCode) {
		CategoryEntity cce = getCategoryEntity(categoryCode);
		
		TestEntity te = getTestEntityAndValidate(testCode);
		te.getCategories().add(cce);
//		te.setCategory(cce);
		
		testMgtPersistence.associateTestToCategory(te);
	}

	private CategoryEntity getCategoryEntity(String categoryName) {
		if (StringUtils.isBlank(categoryName) || StringUtils.isEmpty(categoryName)) {
			logger.warn("Category name not present");
			throw new InvalidArgumentException(CATEGORY_NAME_NOT_PRESENT);
		}
		
		CategoryEntity tce = categoryMgtPersistence.getCategory(categoryName);
		
		if (tce == null) {
			logger.info("Category is not present");
			throw new CategoryException(CATEGORY_NOT_FOUND);
		}
		
		return tce;
	}

	@Override
	public List<Test> getAssociatedTests(String categoryName) {
		CategoryEntity ce = getCategoryEntity(categoryName);
		
		List<TestEntity> testEntities = testMgtPersistence.getAssociatedTests(ce);
		return TestMgtBeanTransformer.getTest(testEntities);
	}
	
		
}
